[OS] Process Abstraction (1)
이 글은 포스텍 박찬익 교수님의 CSED312 운영체제 수업의 강의 내용과 자료를 기반으로 하고 있습니다.
지난 학기에 아키로 고생하고 나니 이제 OS가 찾아왔다.
수업은 아직까지 어떻게 따라가곤 있는데, 과제가 문제다. 아직 배운 것도 없는데 뭘 어떻게 구현해야 할지 모르겠다.
여하튼, OS 2주차 강의 요약이다.
주의 : 전공 서적을 읽어보고 강의 내용을 나름대로 재배치 한 탓에 글의 순서가 PPT 순서와 많이 다르다!
1. Process란?
Process is an instance of a program
한마디로 요약하자면 위와 같다.
그렇다면 instance라는 건 무엇이고 program이라는 건 무엇일까?
1. Program이란?
A program is a set of instructions and associated data that resides on the disk
한가지만 기억하면 된다.
프로그램은 정적이다.
아마 마프(SW 시스템 설계)를 수강하면서 자주 봤을 그림이다.
이렇게 컴파일러-어셈블러-링커를 통해 만들어내는 executable file(a.out)이 바로 program이다.
2. Instance란?
프로그램을 실행하면 어떻게 될까?
위 그림처럼 OS가 프로그램의 instance를 만들어 메모리에 올린다.
이렇게 실행중에 있는 프로그램을 process라 한다.
그리고 당연하게도 같은 프로그램에서 여러 instance를 만들 수 있다!
예를 들어 한글 창을 여러개 킨다던가, 크롬을 여러개 킨다던가.
주의할 점은 각기 다른 process라는 점이다.
참고로 loader가 메모리에 올리는 과정을 수행한다.
3. Process in memory
그러면 프로그램은 메모리에 어떤 형태로 올라갈까?
이렇게 프로그램에서 code와 data를 추출해내고, 남은 공간에 stack과 heap을 만들어서 메모리에 올라간다.
간단하게 각 영역이 어떤 역할을 하는지 적어보자.
- Stack
- Allocate/deallocate by process creation and termination
- Local variables, Arguments
- Heap
- Allocate/deallocate by library calls such as malloc() and free()
- Data
- Initialized Global Variables
- R/W
- Code
- Instructions
- Read Only
그렇다면 왜 이런 구조를 취할까?
우선 코드와 데이터를 분리하는 것은 코드가 Writable해지면 보안에 매우 취약해지기 때문이다.
또한 스택과 힙이 반대방향으로 자라는 이유는 두 영역의 겹칩을 막고, 서로가 필요한 만큼 자유롭게 자랄 수 있게 하기 위해서이다.
특히나 버퍼오버플로우(Buffer Overflow)를 막기 위한 것도 있다고 한다. (마프 Attack lab을 상기하자!)
4. OS 와 Process
OS는 Process를 관리할 책임을 가진다.
그럼 어떻게 관리해야 할까?
기본적인 두가지 개념을 알아보자.
PCB
Data structure to store information for a process
OS도 결국 프로그램이다.
이 말은, OS가 process를 관리할 때 process에 대한 정보가 필요하다는 것이다.
예를 들어 Context Switching을 하고 나서 다시 process로 돌아오기 위해서는 switching 전의 정보를 저장해야 하는데, 이걸 어디다가 저쟝해야 할까?
이 데이터 저장소, 내지는 구조를 PCB(process context block)라고 부른다.
흔히 Process Context 라고 하기도 한다.
PCB를 자세히 분석해보면, 아래의 구성요소로 이루어져 있다.
- Process management info
- Process state: new / ready / running / waiting / halted …
- Program counter: 실행될 다음 instruction의 주소를 가리킴
- CPU registers: accumulator, index register, stack pointer 등등..
- CPU-scheduling information: process priority, some scheduling parameters
- Stack, code, and data segment
- …
- Memory management info
- base and limit register
- page tables or segment tables
- …
- I/O and file management info
- communication ports
- list of I/O devices allocated to the process
- list of open files
- …
Shell
Job Control System
Shell은 커널과 사용자 사이를 이어주는 프로그램이다.
곧, 사용자의 명령어를 커널이 알아들을 수 있게 바꿔서 전달해준다.
주로 Job Control을 할 때 사용한다.
Job이란?
Job이라는 건 예전의 OS들이 job processing(batch)라는 개념을 가지고 동작할 때 만들어진 개념이다.
그런데 이후에 UNIX에서 sharing 기능을 가지는 process라는 개념이 등장하면서, process가 job을 거의 대체하게 되었다.
그래서 Operating System Concepts 9th에서는 아예 두 단어가 혼용될 수 있다고 못박아놨다.
다만 job이 일련의 process들을 하나로 묶는 객체라는 설명도 있다.
거의 동치라고 봐도 될 것 같으나, 문맥에 따라서 적절히 선택에서 해석하는게 좋을 것 같다.
2. Protection
the isolation of potentially misbehaving applications and users so that they do not corrupt other applications or the operating system itself.
OS의 중요한 역할 중 하나는 protection이다.
Protection은 궁극적으로 위험성이 있는 instruction의 실행을 막음으로써 행해진다.
그렇다면 위험성이 있는, 즉 허용되지 않은 instruction을 어떻게 막아야 할까?
그리고 애초에 뭐가 허용되지 않은 instruction일까?
하나하나 알아보자.
1. Dual-Mode Operation
성능을 고려하지 않고, 매우 간단하면서 안전한 로직을 짠다면 어떻게 짤 수 있을까?
아마 Software interpreter가 각 instruction이 디스크에 직접 쓰는지, 다른 프로세스 코드로 branch하는지, 다른 프로세스의 메모리에 접근하는지 등등 허용된 instruction인지를 체크하여 통과한다면 실행하고 그렇지 않으면 멈추는 방법이 있을 것이다.
그러나 여기서 조금 더 성능을 고려하여, 직접적으로 하드웨어(프로세서)에서 instruction을 실행하게 하고 싶다면 어떻게 해야할까?
이 접근법이 바로 dual-mode operation이다.
dual-mode는 user mode, kernel mode로 이루어져 있다.
user mode에서는 프로세서가 각 instruction의 허용 여부을 체크하고 실행한다.
kernel mode에서는 그러한 확인 절차 없이 instruction을 실행한다.
위 회로도에서 보다시피, 프로세서는 1bit를 사용해서 user(0)인지 kernel(1)인지를 판단한다.
만약 허용되지 않은 instruction이라면 bit가 1이 되어 kernel mode로 진입할 것이고, 끝나면 다시 0으로 변경되어 user mode로 돌아올 것이다.
잡지식
intel x86은 4개의 privilege level이 있다!
2. Exception
그럼 그 허용되지 않은 instruction, 즉 exception들은 어떤 것이 있을까?
참고
아래의 내용은 마프(SW 시스템 설계)의 내용이다.
OS ppt 상에서는 간단하게 System calls, Interrupts, Exceptions만 적어놨기에, 좀 더 구체적으로 아는 편이 이해에 도움이 될 것 같아 굳이 찾아 적었다.
또한 ppt 상에서의 Exceptions은 sync exception에서 system calls를 제외한 나머지라고 생각하면 된다.
1. Synchronous Exception
우선 Synchronous exception이 있다.
Synchronous란 현재 instruction의 실행과 관계가 있다는 것을 의미한다.
1. Traps
프로그램에 의해 의도적으로 만들어진 exception
핸들링 이후에 컨트롤이 next instruction으로 돌아온다.
대표적인 예시로는 System call이 있다.
2. Faults
의도하지는 않았지만 회복 가능성이 있는(possbily recoverable) exception
회복에 실패하면 abort 하고, 성공하면 실패한 instruction을 재실행한다.
대표적인 예시로는 page fault(recoverable), floating point exception 등이 있다.
3. Aborts
의도하지 않았고, 회복이 불가능한 exception
당연하게도 프로그램이 강제 종료된다.
대표적인 예시로는 parity error, machine check 가 있다.
잡지식
Invalid memory access의 판단 로직은 MMU를 이용한다
MMU는 위의 그림처럼 address bus 중간에 설치된 하드웨어 칩으로서 두 개의 레지스터를 통해 해당 프로그램의 주소 범위를 저장한다.
해당 프로그램의 주소 범위 밖의 주소값이 MMU에 들어온다면, MMU에서 내부 인터럽트 를 발생시켜 CPU에 신호를 준다
2. Asynchronous Exception
반대로 이 exception은 현재 instruction의 실행과 관계가 없는, 상당히 독특한 exception이다.
1. Interrupts
크게 두가지 종류가 있다.
- Timer interrupt
- Used by the kernel to take back control from program
- I/O interrupt from external device
- Ctrl-C (강제 정지)
- Arrival of a packet from a network
- Arrival of data from a disk
Timer interrupt 를 쓰는 가장 대표적인 예시는 무한 반복이다.
while(true){
// some task
}
무한 반복은 CPU를 비정상적으로 길게 점유한다.
이럴 경우, CPU는 interrupt를 발생시켜 강제로 점유를 뺏는다.
3. Implementing Safe Mode Transfer
그렇다면 처리 가능한 exception들을 어떻게 처리해야 할까?
Exception 처리의 기본적인 아이디어는 프로세서가 현재의 상태를 저장하고 mode를 바꾼 다음 exception을 handling하고, 다시 돌아와서 상태를 restore하는 것이다.
우선 mode 전환부터 알아보자.
mode 전환 매커니즘은 매우 신중히 정해야 한다. 잘못하면 매우 위험한 취약점이 되기 때문이다.
많은 OS는 혼란을 방지하고 에러의 가능성을 줄이기 위해서 mode 전환에 있어서 동일한 sequence of instructions를 사용한다고 한다.
어찌됐든, 메커니즘은 아래의 기능들을 무조건 제공해야만 한다.
- Limited entry into the kernel
- User program cannot be allowed to jump to arbitary locations in the kernel.
- ex) The kernel code for handling the read file system call first checks whether the user program has permission to do so. If not, the kernel should return an error.
- why? A malicious program could jump immediately after the code to perform the check, allowing the program to access to anyone’s file.
- Atomic changes to processor state
- Transitioning between the two is atomic — the mode, program counter, stack, and memory protection are all changed at the same time.
- Transparent, restartable execution
- To the user process, an interrupt is invisible
- System call is just like return from a function call
4. Safe Handling of Exceptions
kernel mode로 전환했다면, 이제 exception을 handling 할 차례이다.
Interrupt vector table
기본적으로 interrupt vector table을 사용한다.
interrupt vector table이란 exception handler들의 첫 instruction 포인터들을 모아둔 배열이다.
processor register가 이 table을 가리키고 있다.
interrupt vector table은 위 그림처럼 몇 가지 section으로 구성된다.
예를 들어 x86에서는
- 0~31 : proccessor exceptions (divide-by-zero, etc…)
- 32~255 : interrupts (timer, keyboard, etc..)
- 64 : system call trap handler
로 구성되어 있다.
Handling 흐름은, Exception이 발생하면 processor는 interrupt vector를 이용해서 적절한 handler를 실행함과 동시에 다른 interrupt를 받지 않도록 설정하고, 끝나면 적절한 반환 또는 종료를 한다.
3. Extended Ver of Process concept
여기까지 글을 쭉 읽었다면 한가지 의문이 들어야 한다.
Kernel mode process는 어디에 위치해야 하지?
간단하다! 프로그램 실행 중에 일어나는 모든 exception/interrupt들은 프로그램의 activity라고 볼 수 있으므로, 프로그램과 같은 context에 존재한다고 봐도 된다.
곧, 이렇게 붙일 수가 있다.
댓글남기기